package com.loadburn.heron.plugin;

import com.google.common.collect.Sets;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
import com.google.inject.Stage;
import com.loadburn.heron.annotations.Decoration;
import com.loadburn.heron.annotations.Heron;
import com.loadburn.heron.annotations.Path;
import com.loadburn.heron.annotations.Show;
import com.loadburn.heron.cache.WebCache;
import com.loadburn.heron.complier.Compilers;
import com.loadburn.heron.complier.HeronTemplates;
import com.loadburn.heron.config.GlobalConfig;
import com.loadburn.heron.exceptions.ScannerException;
import com.loadburn.heron.route.HeronPage;
import com.loadburn.heron.template.Template;
import com.loadburn.heron.utils.StringUtils;
import com.loadburn.heron.utils.TemplateUtils;
import com.loadburn.heron.utils.ScanClassesUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletContext;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.google.inject.matcher.Matchers.annotatedWith;

/**
 * @author slacrey (scstlinfeng@yahoo.com)
 *         Date: 13-11-18
 */
public class ScanWebModulePlugin implements Plugin {

    private static Logger logger = LoggerFactory.getLogger(ScanWebModulePlugin.class);
    private final HeronPage heronPage;
    private final List<Package> packages;
    private final Compilers compilers;

    @Inject
    private final HeronTemplates heronTemplates = null;
    @Inject
    private final Injector injector = null;
    @Inject
    private final Stage currentStage = null;

    private final Provider<WebCache> cacheProvider;
    private final Provider<ServletContext> contextProvider;

    @Inject
    public ScanWebModulePlugin(HeronPage heronPage, Provider<WebCache> cacheProvider,
                               @Heron List<Package> packages, Compilers compilers,
                               Provider<ServletContext> contextProvider) {

        this.contextProvider = contextProvider;
        this.cacheProvider = cacheProvider;
        this.heronPage = heronPage;
        this.packages = packages;
        this.compilers = compilers;
    }

    @Override
    public void startup() {

        // 缓存页面
        if (Stage.DEVELOPMENT != currentStage) {

            if (!StringUtils.empty(GlobalConfig.getWebViewSuffix())) {
                TemplateUtils templateUtils = new TemplateUtils(contextProvider.get(), GlobalConfig.getWebViewSuffix());
                Map<String, Template> templateMap = templateUtils.in(GlobalConfig.getWebViewPrefix(), true);

                cacheProvider.get().put(Template.WEB_TEMPLATE, templateMap);

                logger.info("缓存页面完毕...");
            }
        }

        // 扫描web controller 包
        Set<Class<?>> set = Sets.newHashSet();
        for (Package pkg : packages) {

            set.addAll(ScanClassesUtils.matching(
                    annotatedWith(Path.class).or(
                            annotatedWith(Show.class))
            ).in(pkg));

        }

        Set<HeronPage.Page> pagesToCompile = scanPagesToCompile(set);
        // 装饰器扫描
        decorationPages(pagesToCompile);

        if (Stage.DEVELOPMENT != currentStage) {
            compileToPages(pagesToCompile);
        }
        logger.info("web扫描插件执行完毕...");

    }

    @Override
    public void shutdown() {

        if (cacheProvider.get().getCacheManager() != null) {
            cacheProvider.get().getCacheManager().shutdown();
        }
        logger.info("web缓存关闭...");
    }

    private Set<HeronPage.Page> scanPagesToCompile(Set<Class<?>> set) {

        Set<HeronPage.Page> pagesToCompile = Sets.newHashSet();
        Set<HeronTemplates.TemplateDescription> templates = Sets.newHashSet();
        for (Class<?> pageClass : set) {
            Path path = pageClass.getAnnotation(Path.class);
            if (null != path) {
                pagesToCompile.addAll(heronPage.path(path.value(), pageClass));
            }
            if (pageClass.isAnnotationPresent(Show.class)) {
                templates.add(new HeronTemplates.TemplateDescription(pageClass,
                        pageClass.getAnnotation(Show.class).value()));
            }
        }

        // 预加载页面模板
        if (Stage.DEVELOPMENT != currentStage) {
            this.heronTemplates.loadAll(templates);
        }

        return pagesToCompile;

    }

    /**
     * 装饰器页面
     * @param pagesToCompile 编译器页面集合
     */
    private void decorationPages(Set<HeronPage.Page> pagesToCompile) {

        Set<HeronPage.Page> pageDecorations = Sets.newHashSet();
        for (HeronPage.Page page : pagesToCompile) {
            if (page.pageClass().isAnnotationPresent(Decoration.class)) {
                scanBaseDecoration(pageDecorations, page.pageClass());
            }
        }
        pagesToCompile.addAll(pageDecorations);

    }

    /**
     * 扫描基础装饰器
     * @param pagesToCompile 页面对象集合
     * @param extendClassArgument 父类
     */
    private void scanBaseDecoration(Set<HeronPage.Page> pagesToCompile, final Class<?> extendClassArgument) {

        Class<?> extendClass = extendClassArgument;
        pagesToCompile.add(heronPage.decorate(extendClass));

        while (extendClass != Object.class) {
            extendClass = extendClass.getSuperclass();
            if (extendClass.isAnnotationPresent(Decoration.class)) {
                scanBaseDecoration(pagesToCompile, extendClass);
            } else if (extendClass.isAnnotationPresent(Show.class)) {
                return;
            }
        }

        throw new ScannerException("Could not find super class annotated with @Show on parent of class: " + extendClassArgument);
    }

    /**
     * 给page加上render
     * @param pagesToCompile 页面对象集合
     */
    private void compileToPages(Set<HeronPage.Page> pagesToCompile) {
        for (HeronPage.Page page : pagesToCompile) {
            compilers.compilePage(page);
        }
    }

}
